package br.usp.pcs.basys2.engine;

import java.io.File;
import java.io.IOException;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.JFreeChart;
import org.jfree.data.category.IntervalCategoryDataset;
import org.jfree.data.gantt.Task;
import org.jfree.data.gantt.TaskSeries;
import org.jfree.data.gantt.TaskSeriesCollection;

import br.usp.pcs.basys2.BasysProcess;
import br.usp.pcs.basys2.file.FileManager;
import br.usp.pcs.basys2.file.FileManagerListener;
import br.usp.pcs.basys2.file.BasysFile.AcessInfo;
import br.usp.pcs.basys2.info.JobInfo;
import br.usp.pcs.basys2.job.JobManager;
import br.usp.pcs.basys2.job.JobManagerListener;
import br.usp.pcs.basys2.memory.MemoryManager;
import br.usp.pcs.basys2.memory.MemoryManagerListener;
import br.usp.pcs.basys2.peripheral.PeripheralManager;
import br.usp.pcs.basys2.peripheral.PeripheralManagerListener;
import br.usp.pcs.basys2.processor.ProcessorManager;
import br.usp.pcs.basys2.processor.ProcessorManagerListener;

public class OutputDataGenerator {

	private MemoryManager memoryManager;
	private ProcessorManager processorManager;
	private PeripheralManager peripheralManager;
	private FileManager fileManager;
	private JobManager jobManager;

	final TaskSeriesCollection collection = new TaskSeriesCollection();
	final TaskSeries memoryTasks = new TaskSeries("Memory");
	final TaskSeries processTasks = new TaskSeries("Process");
	final Map<String, TaskSeries> peripheralTasksMap = new HashMap<String, TaskSeries>();
	final Map<String, TaskSeries> fileTasksMap = new HashMap<String, TaskSeries>();

	public void addSystemListeners(BasysSystem system) {
		memoryManager = system.getMemoryManager();
		processorManager = system.getProcessorManager();
		peripheralManager = system.getPeripheralManager();
		fileManager = system.getFileManager();
		jobManager = system.getJobManager();
		defineMemoryManagerListener();
		defineProcessorManagerListener();
		definePeripheralManagerListener();
		defineFileManagerListener();
		defineJobManagerFlow();

	}

	private void defineJobManagerFlow() {
		jobManager.add(new JobManagerListener() {

			public void onJobIsActivated(JobInfo job) {
				String effect = "Job " + job.getTopProcess().getId()
						+ " is Activated";
				Engine.getInstance().getCurrentOutputBlock().add(effect);
			}

			public void onJobIsDeactivated(JobInfo job) {
				String effect = "Job " + job.getTopProcess().getId()
						+ " is Deactivated";
				Engine.getInstance().getCurrentOutputBlock().add(effect);
			}

			public void onJobIsQueued(JobInfo job) {
				String effect = "Job " + job.getTopProcess().getId()
						+ " is Queued";
				Engine.getInstance().getCurrentOutputBlock().add(effect);
			}

		});
	}

	protected void defineMemoryManagerListener() {
		memoryManager.add(new MemoryManagerListener() {

			private Map<BasysProcess, Calendar> map = new HashMap<BasysProcess, Calendar>();

			public void onProccesIsAllocated(BasysProcess basysProcess) {
				Calendar currentDate = Engine.getCurrentDate();
				String effect = "Process " + basysProcess + " is Allocated";
				Engine.getInstance().getCurrentOutputBlock().add(effect);
				map.put(basysProcess, currentDate);
			}

			public void onProccesIsDeallocated(BasysProcess basysProcess) {
				Calendar startDate = map.get(basysProcess);
				map.remove(basysProcess);
				Calendar endDate = Engine.getCurrentDate();
				String effect = "Process " + basysProcess + " is Deallocated";
				Engine.getInstance().getCurrentOutputBlock().add(effect);
				Task task = new Task("Process " + basysProcess, startDate
						.getTime(), endDate.getTime());
				memoryTasks.add(task);
			}

		});
	}

	protected void defineProcessorManagerListener() {
		processorManager.add(new ProcessorManagerListener() {

			private Map<BasysProcess, Calendar> map = new HashMap<BasysProcess, Calendar>();
			private Map<BasysProcess, Task> taskMap = new HashMap<BasysProcess, Task>();

			public void onProcessStartedRunning(BasysProcess basysProcess,
					Integer timeSlice) {
				Calendar currentDate = Engine.getCurrentDate();
				map.put(basysProcess, currentDate);
				String effect = "Process " + basysProcess
						+ " has Started Running";
				Engine.getInstance().getCurrentOutputBlock().add(effect);
			}

			public void onProcessStoppedRunning(BasysProcess basysProcess) {
				Calendar startDate = map.get(basysProcess);
				if (startDate == null) {
					throw new IllegalStateException("Process " + basysProcess
							+ " is not started.");
				}
				map.remove(basysProcess);
				Calendar endDate = Engine.getCurrentDate();

				long spacer = 10L;
				Date endTime = new Date(endDate.getTimeInMillis() - spacer/2);
				Date startTime = new Date(startDate.getTimeInMillis() + spacer/2);
				Task task = new Task("Process " + basysProcess, startTime,
						endTime);

				if (!taskMap.containsKey(basysProcess)) {
					taskMap.put(basysProcess, task);
					processTasks.add(task);
					task.addSubtask(task);
				} else {
					taskMap.get(basysProcess).addSubtask(task);
					// processTasks.add(task);
				}
				String effect = "Process " + basysProcess
						+ " has Stopped Running";
				Engine.getInstance().getCurrentOutputBlock().add(effect);
			}

			public void onProcessIsFinished(BasysProcess basysProcess) {
				/* this.onProcessStoppedRunning(basysProcess); */
				String effect = "Process " + basysProcess + " has Finished";
				Engine.getInstance().getCurrentOutputBlock().add(effect);
			}

		});
	}

	protected void definePeripheralManagerListener() {
		peripheralManager
				.addListenerToPeripherals(new PeripheralManagerListener() {

					class StartedAcessInfo {
						private BasysProcess process;
						private String peripheralName;

						public StartedAcessInfo(BasysProcess process,
								String peripheralName) {
							super();
							this.process = process;
							this.peripheralName = peripheralName;
						}

						@Override
						public int hashCode() {
							final int prime = 31;
							int result = 1;
							result = prime
									* result
									+ ((peripheralName == null) ? 0
											: peripheralName.hashCode());
							result = prime
									* result
									+ ((process == null) ? 0 : process
											.hashCode());
							return result;
						}

						@Override
						public boolean equals(Object obj) {
							if (this == obj)
								return true;
							if (obj == null)
								return false;
							if (getClass() != obj.getClass())
								return false;
							final StartedAcessInfo other = (StartedAcessInfo) obj;
							if (peripheralName == null) {
								if (other.peripheralName != null)
									return false;
							} else if (!peripheralName
									.equals(other.peripheralName))
								return false;
							if (process == null) {
								if (other.process != null)
									return false;
							} else if (!process.equals(other.process))
								return false;
							return true;
						}

					}

					private Map<StartedAcessInfo, Calendar> map = new HashMap<StartedAcessInfo, Calendar>();

					public void onProcesStartedAccessingPeripheral(
							BasysProcess basysProcess, String peripheralName) {
						StartedAcessInfo startedAcessInfo = new StartedAcessInfo(
								basysProcess, peripheralName);
						Calendar currentDate = Engine.getCurrentDate();
						map.put(startedAcessInfo, currentDate);
						String effect = "Process " + basysProcess
								+ " has Started Accessing " + peripheralName;
						Engine.getInstance().getCurrentOutputBlock()
								.add(effect);
					}

					public void onProcesStopedAccessingPeripheral(
							BasysProcess basysProcess, String peripheralName) {
						StartedAcessInfo startedAcessInfo = new StartedAcessInfo(
								basysProcess, peripheralName);
						Calendar startDate = map.get(startedAcessInfo);
						map.remove(startedAcessInfo);
						Calendar endDate = Engine.getCurrentDate();
						Task task = new Task("Process " + basysProcess,
								startDate.getTime(), endDate.getTime());
						if (!peripheralTasksMap.containsKey(peripheralName)) {
							peripheralTasksMap.put(peripheralName,
									new TaskSeries(peripheralName));
						}
						String effect = "Process " + basysProcess
								+ " has Stopped Accessing " + peripheralName;
						Engine.getInstance().getCurrentOutputBlock()
								.add(effect);
						TaskSeries taskSeries = peripheralTasksMap
								.get(peripheralName);
						taskSeries.add(task);
					}

				});
	}

	protected void defineFileManagerListener() {
		fileManager.addListenerToFiles(new FileManagerListener() {

			private Map<AcessInfo, Calendar> map = new HashMap<AcessInfo, Calendar>();

			public void onProcesStartedAccessingFile(AcessInfo acessInfo) {
				BasysProcess basysProcess = acessInfo.getProcess();
				String fileName = acessInfo.getActivityInfo().getFileName();
				Calendar currentDate = Engine.getCurrentDate();
				map.put(acessInfo, currentDate);
				String effect = "Process " + basysProcess
						+ " has Started Accessing File " + fileName;
				Engine.getInstance().getCurrentOutputBlock().add(effect);

			}

			public void onProcesStopedAccessingFile(AcessInfo acessInfo) {
				BasysProcess basysProcess = acessInfo.getProcess();
				String fileName = acessInfo.getActivityInfo().getFileName();
				Calendar startDate = map.get(acessInfo);
				map.remove(acessInfo);
				Calendar endDate = Engine.getCurrentDate();
				Task task = new Task("Process " + basysProcess, startDate
						.getTime(), endDate.getTime());
				if (!fileTasksMap.containsKey(fileName)) {
					fileTasksMap.put(fileName, new TaskSeries(fileName));
				}
				TaskSeries taskSeries = fileTasksMap.get(fileName);
				taskSeries.add(task);
				String effect = "Process " + basysProcess
						+ " has Stopped Accessing File " + fileName;
				Engine.getInstance().getCurrentOutputBlock().add(effect);

			}

		});
	}

	private JFreeChart createChart(final IntervalCategoryDataset dataset,
			String inputString) {
		final JFreeChart chart = ChartFactory.createGanttChart(
				"Basys2 Gantt Chart For (" + inputString + ")", // chart
				// title
				"Task", // domain axis label
				"Date", // range axis label
				dataset, // data
				true, // include legend
				true, // tooltips
				false // urls
				);
		// chart.getCategoryPlot().getDomainAxis()
		// .setMaximumCategoryLabelWidthRatio(10.0f);

		return chart;
	}

	public void exportToPng(String fileName, String inputString) {
		File file = new File(fileName);
		collection.add(memoryTasks);
		collection.add(processTasks);
		for (String s : peripheralTasksMap.keySet()) {
			collection.add(peripheralTasksMap.get(s));
		}
		for (String s : fileTasksMap.keySet()) {
			collection.add(fileTasksMap.get(s));
		}
		JFreeChart chart = this.createChart(collection, inputString);
		int width = 1000;
		int height = 600;
		try {
			ChartUtilities.saveChartAsPNG(file, chart, width, height);
		} catch (IOException e) {
			e.printStackTrace();
		}

	}

}
